<?php
// +----------------------------------------------------------------------
// | INPHP
// | Copyright (c) 2023 https://inphp.cc All rights reserved.
// | Licensed ( https://opensource.org/licenses/MIT )
// | Author: 幺月儿(https://gitee.com/lulanyin) Email: inphp@qq.com
// +----------------------------------------------------------------------
// | 客户端对象
// +----------------------------------------------------------------------
namespace Inphp\Core\Object;

use Inphp\Core\Config;
use Inphp\Core\Db\Redis;
use Inphp\Core\Service;
use Inphp\Core\Services\Http\Session;

class Client
{
    /**
     * 客户端ID
     * 可以使用 swoole request 对象的 fd
     * 也可以自定义
     * @var string|int
     */
    public string|int $id = -1;

    /**
     * PHP_SESSION_ID
     * 若使用的是swoole服务，则需要另行实现
     * @var string|null
     */
    public ?string $sessionId = null;

    /**
     * 请求域名
     * @var string
     */
    public string $host = "127.0.0.1";

    /**
     * 是不是用https访问
     * @var bool
     */
    public bool $https = false;

    /**
     * 来源域名
     * @var string
     */
    public string $origin = "127.0.0.1";

    /**
     * 请求方式
     * @var string
     */
    public string $method = "GET";

    /**
     * 客户语言（暂支持中文）
     * @var string
     */
    public string $lang = "zh";

    /**
     * $_SERVER
     * @var array
     */
    public array $server = [];

    /**
     * cookie
     * @var array
     */
    public array $cookies = [];

    /**
     * session
     * @var array
     */
    public array $sessions = [];

    /**
     * 地址参数
     * @var array
     */
    public array $get = [];

    /**
     * post参数
     * @var array
     */
    public array $post = [];

    /**
     * GET和POST的参数集
     * @var array
     */
    public array $request = [];

    /**
     * HTTP_RAW_POST_DATA
     * @var string
     */
    public string $rawData = "";

    /**
     * 文件上传
     * @var array
     */
    public array $files = [];

    /**
     * 客户端IP
     * @var string
     */
    public string $ip = "127.0.0.1";

    /**
     * 请求路径
     * @var string
     */
    public string $uri = "";

    /**
     * 其它扩展数据
     * @var array 
     */
    public array $otherData = [];

    /**
     * 客户端传递过来的头部数据
     * @var array
     */
    public array $header = [];

    /**
     * 客户端的请求内容类型
     * @var string
     */
    public string $contentType = "";

    /**
     * 客户端的worker id
     * @var int
     */
    public int $workerId = 0;

    /**
     * 是否保存到缓存，如果保存到缓存，每次修改、保存数据的时候，都会更新一次缓存
     * @var bool
     */
    public bool $cache = false;

    /**
     * 是否跨域请求，即 origin != host
     * @var bool
     */
    public bool $cross = false;

    /**
     * 需要保存到缓存的数据
     * @var array|string[]
     */
    public array $cacheKeys = ["host", "origin", "ip", "method", "get", "cookies", "https", "id", "workerId", "otherData"];

    /**
     * Client constructor.
     * @param array $values
     * @param bool $cache
     */
    public function __construct(array $values = [], bool $cache = false)
    {
        $this->host     = $values["host"] ?? "127.0.0.1";
        $this->origin   = $values["origin"] ?? "";
        $this->method   = strtoupper($values["method"] ?? "get");
        $this->ip       = $values["ip"] ?? "127.0.0.1";
        $this->get      = $values["get"] ?? [];
        $this->post     = $values["post"] ?? [];
        $this->files    = $values["files"] ?? [];
        $this->cookies  = $values["cookie"] ?? [];
        $this->sessions = $values["session"] ?? [];
        $this->rawData  = $values["rawData"] ?? "";
        $this->uri      = $values["uri"] ?? "";
        $this->https    = $values["https"] ?? false;
        $this->id       = $values["id"] ?? -1;
        $this->sessionId = $values["sessionId"] ?? null;
        $this->server   = $values["server"] ?? [];
        $this->otherData= $values["otherData"] ?? $this->otherData;
        $this->header   = $values["header"] ?? [];
        $this->contentType = strtolower($values["contentType"] ?? "");
        $this->workerId = $values["workerId"] ?? 0;
        $this->cache    = $cache;
        $this->cross    = !empty($this->origin) && $this->origin != ($this->https ? "https://" : "http://").$this->host;
        //
        //如果是POST请求，并带有 json 数据
        if ($this->method === "POST" && !empty($this->rawData) && stripos($this->rawData, "{") === 0 && strrchr($this->rawData, "}") === "}") {
            $data = @json_decode($this->rawData, true) ?? [];
            $this->post = array_merge($this->post, $data);
        }
        $this->request = array_merge($this->get, $this->post);
        //
        if ($cache) {
            $this->saveToCache();
        }
    }

    /**
     * 生成缓存名
     * @param int $workerId
     * @param int $id
     * @return string
     */
    public static function getCacheKeyName(int $workerId, int $id): string
    {
        return join("_", ["client", $workerId, $id]);
    }

    /**
     * 保存到redis缓存，仅保存部分数据
     * 仅保存属性
     */
    public function saveToCache(): void
    {
        $values = [];
        foreach ($this->cacheKeys as $key) {
            $values[$key] = $this->get($key);
        }
        Redis::set(self::getCacheKeyName($this->workerId, $this->id), $values);
    }

    /**
     * 从缓存获取数据
     * @param int $workerId
     * @param int $id
     * @return Client|null
     */
    public static function fromCache(int $workerId, int $id): ?Client
    {
        $data = Redis::get(self::getCacheKeyName($workerId, $id));
        if (!empty($data) && is_array($data) && isset($data["workerId"]) && isset($data["id"])) {
            $client = new Client($data);
            $client->cache = true;
            return $client;
        }
        return null;
    }

    /**
     * 移除客户端缓存数据
     * @param int $workerId
     * @param int $id
     */
    public static function remove(int $workerId, int $id): void
    {
        if (!Service::isCLI()) {
            return;
        }
        Redis::del(self::getCacheKeyName($workerId, $id));
    }

    /**
     * 获取属性值
     * @param $name
     * @return mixed
     */
    public function get($name): mixed
    {
        return property_exists($this, $name) ? $this->{$name} : null;
    }

    /**
     * 设置值
     * @param string $name
     * @param mixed $value
     */
    public function set(string $name, mixed $value): void
    {
        $this->{$name} = $value;
        if ($this->cache && in_array($name, $this->cacheKeys)) {
            $this->saveToCache();
        }
    }

    /**
     * 保存数据到拓展数据
     * @param string $name
     * @param mixed $value
     */
    public function setOtherData(string $name, mixed $value): void
    {
        $this->otherData[$name] = $value;
        if ($this->cache && in_array("otherData", $this->cacheKeys)) {
            $this->saveToCache();
        }
    }

    /**
     * 获取cookie值
     * @param string $name
     * @param mixed $default
     * @return mixed
     */
    public function getCookie(string $name, mixed $default = null): mixed
    {
        if (!isset($this->cookies[$name]) || !isset($this->cookies[$name."_hash"])) {
            return $default;
        }
        //当前时间
        $now = time();
        //原值
        $value = $this->cookies[$name];
        $value = is_array($value) ? ($value["time"] > $now ? $value["value"] : null) : $value;
        //加密校验值
        $hash = $this->cookies[$name."_hash"];
        $hash = is_array($hash) ? ($hash["time"] > $now ? $hash["value"] : null) : $hash;
        if (!is_null($value) && !is_null($hash)) {
            $key = Config::get("server.cookieHashKey", "1i3m5");
            if (strtoupper(substr(hash_hmac("sha1", $value, $key), 0, 32)) == $hash) {
                //对比正确
                return $value;
            }
        }
        return $default;
    }

    /**
     * 保存Cookie
     * @param string $name
     * @param string|null $value
     * @param int $seconds
     */
    public function setCookie(string $name, ?string $value = null, int $seconds = 0): void
    {
        if (is_null($value)) {
            $this->cookies[$name] = [
                "time"      => 0,
                "value"     => null
            ];
            $this->cookies[$name."_hash"] = [
                "time"      => 0,
                "value"     => null
            ];
        } else {
            $server = Config::get("server");
            $seconds = $seconds != 0 ? $seconds : ($server["cookieLifeTime"] ?? 86400);
            $seconds = $seconds > 0 ? $seconds : 0;
            $hashKey = $server["cookieHashKey"] ?? "1i3m5";
            $hash = substr(hash_hmac("sha1", $value, $hashKey), 0, 32);
            $now = time();
            $this->cookies[$name] = [
                "time"      => $seconds > 0 ? ($now + $seconds) : 0,
                "value"     => $value
            ];
            $this->cookies[$name."_hash"] = [
                "time"      => $seconds > 0 ? ($now + $seconds) : 0,
                "value"     => strtoupper($hash)
            ];
        }
        if ($this->cache && in_array("cookies", $this->cacheKeys)) {
            $this->saveToCache();
        }
    }

    /**
     * 获取所有cookie的值
     * @return array
     */
    public function getCookieValues(): array
    {
        $list = [];
        $now = time();
        foreach ($this->cookies as $name => $value) {
            if (is_array($value) && $value["time"] > $now) {
                $value = $value["value"];
            }
            $list[$name] = $value;
        }
        return $list;
    }

    /**
     * 删除cookie
     * @param string $name
     */
    public function deleteCookie(string $name): void
    {
        $this->setCookie($name);
    }

    /**
     * 获取 session_id
     * @return string
     */
    public function getSessionId(): string
    {
        $sessionId = $this->sessionId ?? $this->getCookie("SESSION_ID");
        if (empty($sessionId)) {
            //sha1(时间+随机字符+客户端IP) 40个字符长度，但只截取32个字符
            return $this->setSessionId();
        }
        return $sessionId;
    }

    /**
     * 初始化一个session_id
     * @return string
     */
    private function setSessionId(): string
    {
        if (Service::isCLI()) {
            $this->sessionId = strtoupper(substr(sha1(microtime(true).rand(0, 999999).$this->ip), 0, 32));
            $this->setCookie("SESSION_ID", $this->sessionId, Config::get("server.sessionLifeTime"));
        } else {
            if (session_status() != PHP_SESSION_ACTIVE) {
                @session_start();
            }
            $this->sessionId = session_id();
        }
        if ($this->cache && in_array("sessionId", $this->cacheKeys)) {
            $this->saveToCache();
        }
        return $this->sessionId;
    }

    /**
     * 自动延期 session id
     * 仅对 CLI 有效
     */
    private function delaySessionId(): void
    {
        if (Service::isCLI()) {
            $this->setCookie("SESSION_ID", $this->getSessionId(), Config::get("server.sessionLifeTime"));
        }
    }

    /**
     * 获取token
     * @return ?string
     */
    public function getToken(): ?string
    {
        return Session::get("token") ?? ($this->get["token"] ?? ($this->post["token"] ?? $this->getCookie("token")));
    }
}
